using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

namespace unTroublemaker.Parsing
{
    public class SpecificationParser : ISpecificationParser
    {
        private readonly List<ISpecificationParser> _parsers = new List<ISpecificationParser>();

        public SpecificationParser()
        {
            LoadParsers(GetType().Assembly);
        }

        private void LoadParsers(Assembly assembly)
        {
            var parserLoader = new ParserLoader(assembly);
            if(parserLoader.HasErrors)
                throw new Exception("Failed to load parsers from " + assembly.FullName + ": " + parserLoader.Error);

            _parsers.AddRange(parserLoader.Parsers);

            var parserVerifier = new ParserVerifier(_parsers);
            if(parserVerifier.HasErrors)
                throw new Exception("Error loading parsers from " + assembly.FullName + ": " + parserVerifier.Error);
        }

        public ISpecificationItem[] Parse(XElement element)
        {
            element.AssertName("specification");

            var conditions = new List<ISpecificationItem>();
            foreach (var child in element.Elements())
                conditions.Add(Parse(child, this));

            return conditions.ToArray();
        }

        private bool TryFindParser(XElement element, out ISpecificationParser parser)
        {
            parser = _parsers.SingleOrDefault(p => element.HasName(p.ElementName));
            return parser != null;
        }

        public ISpecificationItem Parse(XElement element, ISpecificationParser genericParser)
        {
            ISpecificationParser parser;
            if (!TryFindParser(element, out parser))
                throw new ParseException(element, "Failed to find parser for element name");

            try
            {
                return parser.Parse(element, this);
            }
            catch (Exception e)
            {
                throw new ParseException(element, "Unhandled error occured whilst parsing node, see inner exception", e);
            }
        }

        public string ElementName { get { return "specification"; } }
    }
}